package xorm

import (
	"database/sql"
	"gitee.com/KotlinToGo/xorm/internal/utils"
	"strings"
	"time"
)

type SqlExecutor struct {
	*Session
	sql    interface{}
	args  interface{}
	err     error
}

func (s *SqlExecutor) Execute() ([][]map[string]interface{}, map[string][]map[string]interface{}, error) {
	defer s.resetStatement()
	defer s.Close()

	if s.err != nil {
		return nil, nil, s.err
	}
	var (
		r1 *ResultMap
		r2 sql.Result
		err error
	)
	sqlModel := 1

	if s.isSqlFunc == true {
		err := s.Begin()
		if err != nil {
			return nil, nil, err
		}
	}

	switch s.sql.(type) {
	case string:
		sqlStr := strings.TrimSpace(s.sql.(string))
		sqlCmd := strings.ToLower(strings.Split(sqlStr, " ")[0])

		if s.args == nil {
			switch sqlCmd {
			case "select":
				r1 = s.Sql(sqlStr).Query()
				sqlModel = 1
			case "insert", "delete", "update", "create", "drop":
				r2, err = s.Sql(sqlStr).Execute()
				sqlModel = 2
			default:
				sqlModel = 3
			}
		} else {
			switch s.args.(type) {
			case []map[string]interface{}:
				parmaMap, _ := s.args.([]map[string]interface{})

				key := utils.NewV4().String() + time.Now().String()
				s.engine.AddSql(key, sqlStr)
				switch sqlCmd {
				case "select":
					r1 = s.SqlMapClient(key, &parmaMap[0]).Query()
					sqlModel = 1
				case "insert", "delete", "update", "create", "drop":
					r2, err = s.SqlMapClient(key, &parmaMap[0]).Execute()
					sqlModel = 2
				default:
					sqlModel = 3

				}
				s.engine.RemoveSql(key)
			case map[string]interface{}:
				parmaMap, _ := s.args.(map[string]interface{})

				key := utils.NewV4().String() + time.Now().String()
				s.engine.AddSql(key, sqlStr)
				switch sqlCmd {
				case "select":
					r1 = s.SqlMapClient(key, &parmaMap).Query()
					sqlModel = 1
				case "insert", "delete", "update", "create", "drop":
					r2, err = s.SqlMapClient(key, &parmaMap).Execute()
					sqlModel = 2
				default:
					sqlModel = 3
				}
				s.engine.RemoveSql(key)
			default:
				if s.isSqlFunc == true {
					err1 := s.Rollback()
					if err1 != nil {
						return nil, nil, err1
					}
				}
				return nil, nil, ErrParamsType
			}
		}
		s.isSqlFunc = true
		resultSlice := make([][]map[string]interface{}, 1)

		if sqlModel == 1 {
			if r1.Error != nil {
				if s.isSqlFunc == true {
					err1 := s.Rollback()
					if err1 != nil {
						return nil, nil, err1
					}
				}
				return nil, nil, r1.Error
			}

			resultSlice[0] = make([]map[string]interface{}, len(r1.Result))
			resultSlice[0] = r1.Result
			if s.isSqlFunc == true {
				err1 := s.Commit()
				if err1 != nil {
					return nil, nil, err1
				}
			}
			return resultSlice, nil, nil
		} else if sqlModel == 2 {
			if err != nil {
				if s.isSqlFunc == true {
					err1 := s.Rollback()
					if err1 != nil {
						return nil, nil, err1
					}
				}
				return nil, nil, err
			}

			resultMap := make([]map[string]interface{}, 1)
			resultMap[0] = make(map[string]interface{})

			//todo all database support LastInsertId
			LastInsertId, _ := r2.LastInsertId()

			resultMap[0]["LastInsertId"] = LastInsertId
			RowsAffected, err := r2.RowsAffected()
			if err != nil {
				return nil, nil, err
			}
			resultMap[0]["RowsAffected"] = RowsAffected
			resultSlice[0] = resultMap
			if s.isSqlFunc == true {
				err1 := s.Commit()
				if err1 != nil {
					return nil, nil, err1
				}
			}
			return resultSlice, nil, nil
		} else {
			resultSlice[0] = nil
		}
	case []string:

		sqlsSlice := s.sql.([]string)
		n := len(sqlsSlice)
		resultSlice := make([][]map[string]interface{}, n)
		parmaSlice := make([]map[string]interface{}, n)

		if s.args == nil {
			for i, _ := range sqlsSlice {
				s.isSqlFunc = true
				sqlStr := strings.TrimSpace(sqlsSlice[i])
				sqlCmd := strings.ToLower(strings.Split(sqlStr, " ")[0])
				switch sqlCmd {
				case "select":
					r1 = s.Sql(sqlStr).Query()
					sqlModel = 1
				case "insert", "delete", "update", "create", "drop":
					r2, err = s.Sql(sqlStr).Execute()
					sqlModel = 2
				default:
					sqlModel = 3
				}
				s.isSqlFunc = true
				if sqlModel == 1 {
					if r1.Error != nil {
						if s.isSqlFunc == true {
							err1 := s.Rollback()
							if err1 != nil {
								return nil, nil, err1
							}
						}
						return nil, nil, r1.Error
					}

					resultSlice[i] = r1.Result

				} else if sqlModel == 2 {
					if err != nil {
						if s.isSqlFunc == true {
							err1 := s.Rollback()
							if err1 != nil {
								return nil, nil, err1
							}
						}
						return nil, nil, err
					}

					resultMap := make([]map[string]interface{}, 1)
					resultMap[0] = make(map[string]interface{})

					//todo all database support LastInsertId
					LastInsertId, _ := r2.LastInsertId()

					resultMap[0]["LastInsertId"] = LastInsertId
					RowsAffected, err := r2.RowsAffected()
					if err != nil {
						if s.isSqlFunc == true {
							err1 := s.Rollback()
							if err1 != nil {
								return nil, nil, err1
							}
						}
						return nil, nil, err
					}
					resultMap[0]["RowsAffected"] = RowsAffected
					resultSlice[i] = make([]map[string]interface{}, 1)
					resultSlice[i] = resultMap

				} else {
					resultSlice[i] = nil
				}

			}

		} else {
			switch s.args.(type) {
			case []map[string]interface{}:
				parmaSlice = s.args.([]map[string]interface{})

			default:
				if s.isSqlFunc == true {
					err1 := s.Rollback()
					if err1 != nil {
						return nil, nil, err1
					}
				}
				return nil, nil, ErrParamsType
			}

			for i, _ := range sqlsSlice {
				s.isSqlFunc = true
				sqlStr := strings.TrimSpace(sqlsSlice[i])
				sqlCmd := strings.ToLower(strings.Split(sqlStr, " ")[0])
				if parmaSlice[i] == nil {
					switch sqlCmd {
					case "select":
						r1 = s.Sql(sqlStr).Query()
						sqlModel = 1
					case "insert", "delete", "update", "create", "drop":
						r2, err = s.Sql(sqlStr).Execute()
						sqlModel = 2
					default:
						sqlModel = 3
					}
				} else {
					key := utils.NewV4().String() + time.Now().String()
					s.engine.AddSql(key, sqlStr)
					switch sqlCmd {
					case "select":
						r1 = s.SqlMapClient(key, &parmaSlice[i]).Query()
						sqlModel = 1
					case "insert", "delete", "update", "create", "drop":
						r2, err = s.SqlMapClient(key, &parmaSlice[i]).Execute()
						sqlModel = 2
					default:
						sqlModel = 3
					}
					s.engine.RemoveSql(key)
				}
				s.isSqlFunc = true
				if sqlModel == 1 {
					if r1.Error != nil {
						if s.isSqlFunc == true {
							err1 := s.Rollback()
							if err1 != nil {
								return nil, nil, err1
							}
						}
						return nil, nil, r1.Error
					}

					resultSlice[i] = r1.Result

				} else if sqlModel == 2 {
					if err != nil {
						if s.isSqlFunc == true {
							err1 := s.Rollback()
							if err1 != nil {
								return nil, nil, err1
							}
						}
						return nil, nil, err
					}

					resultMap := make([]map[string]interface{}, 1)
					resultMap[0] = make(map[string]interface{})

					//todo all database support LastInsertId
					LastInsertId, _ := r2.LastInsertId()

					resultMap[0]["LastInsertId"] = LastInsertId
					RowsAffected, err := r2.RowsAffected()
					if err != nil {
						if s.isSqlFunc == true {
							err1 := s.Rollback()
							if err1 != nil {
								return nil, nil, err1
							}
						}
						return nil, nil, err
					}
					resultMap[0]["RowsAffected"] = RowsAffected
					resultSlice[i] = make([]map[string]interface{}, 1)
					resultSlice[i] = resultMap

				} else {
					resultSlice[i] = nil
				}
			}

		}

		if s.isSqlFunc == true {
			err1 := s.Commit()
			if err1 != nil {
				return nil, nil, err1
			}
		}
		return resultSlice, nil, nil

	case map[string]string:

		sqlsMap := s.sql.(map[string]string)
		n := len(sqlsMap)
		resultsMap := make(map[string][]map[string]interface{}, n)
		parmasMap := make(map[string]map[string]interface{}, n)

		if s.args == nil {
			for k, _ := range sqlsMap {
				s.isSqlFunc = true
				sqlStr := strings.TrimSpace(sqlsMap[k])
				sqlCmd := strings.ToLower(strings.Split(sqlStr, " ")[0])

				switch sqlCmd {
				case "select":
					sqlModel = 1
					r1 = s.Sql(sqlStr).Query()

				case "insert", "delete", "update", "create", "drop":
					sqlModel = 2
					r2, err = s.Sql(sqlStr).Execute()

				default:
					sqlModel = 3
				}
				s.isSqlFunc = true
				if sqlModel == 1 {
					if r1.Error != nil {
						if s.isSqlFunc == true {
							err1 := s.Rollback()
							if err1 != nil {
								return nil, nil, err1
							}
						}
						return nil, nil, r1.Error
					}

					resultsMap[k] = r1.Result

				} else if sqlModel == 2 {
					if err != nil {
						if s.isSqlFunc == true {
							err1 := s.Rollback()
							if err1 != nil {
								return nil, nil, err1
							}
						}
						return nil, nil, err
					}

					resultMap := make([]map[string]interface{}, 1)
					resultMap[0] = make(map[string]interface{})

					//todo all database support LastInsertId
					LastInsertId, _ := r2.LastInsertId()

					resultMap[0]["LastInsertId"] = LastInsertId
					RowsAffected, err := r2.RowsAffected()
					if err != nil {

						if s.isSqlFunc == true {
							err1 := s.Rollback()
							if err1 != nil {
								return nil, nil, err1
							}
						}
						return nil, nil, err
					}
					resultMap[0]["RowsAffected"] = RowsAffected
					resultsMap[k] = make([]map[string]interface{}, 1)
					resultsMap[k] = resultMap

				} else {
					resultsMap[k] = nil
				}

			}

		} else {
			switch s.args.(type) {
			case map[string]map[string]interface{}:
				parmasMap = s.args.(map[string]map[string]interface{})

			default:
				if s.isSqlFunc == true {
					err1 := s.Rollback()
					if err1 != nil {
						return nil, nil, err1
					}
				}
				return nil, nil, ErrParamsType
			}

			for k, _ := range sqlsMap {
				s.isSqlFunc = true
				sqlStr := strings.TrimSpace(sqlsMap[k])
				sqlCmd := strings.ToLower(strings.Split(sqlStr, " ")[0])
				if parmasMap[k] == nil {
					switch sqlCmd {
					case "select":
						sqlModel = 1
						r1 = s.Sql(sqlStr).Query()

					case "insert", "delete", "update", "create", "drop":
						sqlModel = 2
						r2, err = s.Sql(sqlStr).Execute()

					default:
						sqlModel = 3
					}
				} else {
					key := utils.NewV4().String() + time.Now().String()
					s.engine.AddSql(key, sqlStr)
					parmaMap := parmasMap[k]
					switch sqlCmd {
					case "select":
						sqlModel = 1
						r1 = s.SqlMapClient(key, &parmaMap).Query()

					case "insert", "delete", "update", "create", "drop":
						sqlModel = 2
						r2, err = s.SqlMapClient(key, &parmaMap).Execute()

					default:
						sqlModel = 3
					}
					s.engine.RemoveSql(key)
				}
				s.isSqlFunc = true
				if sqlModel == 1 {
					if r1.Error != nil {
						if s.isSqlFunc == true {
							err1 := s.Rollback()
							if err1 != nil {
								return nil, nil, err1
							}
						}
						return nil, nil, r1.Error
					}

					resultsMap[k] = r1.Result

				} else if sqlModel == 2 {
					if err != nil {
						if s.isSqlFunc == true {
							err1 := s.Rollback()
							if err1 != nil {
								return nil, nil, err1
							}
						}
						return nil, nil, err
					}

					resultMap := make([]map[string]interface{}, 1)
					resultMap[0] = make(map[string]interface{})

					//todo all database support LastInsertId
					LastInsertId, _ := r2.LastInsertId()

					resultMap[0]["LastInsertId"] = LastInsertId
					RowsAffected, err := r2.RowsAffected()
					if err != nil {
						if s.isSqlFunc == true {
							err1 := s.Rollback()
							if err1 != nil {
								return nil, nil, err1
							}
						}
						return nil, nil, err
					}
					resultMap[0]["RowsAffected"] = RowsAffected
					resultsMap[k] = make([]map[string]interface{}, 1)
					resultsMap[k] = resultMap

				} else {
					resultsMap[k] = nil
				}
			}

		}
		if s.isSqlFunc == true {
			err1 := s.Commit()
			if err1 != nil {
				return nil, nil, err1
			}
		}
		return nil, resultsMap, nil
	}
	return nil, nil, nil
}
